package com.tpvlog.drc.server.node.master;

import com.tpvlog.drc.server.config.Configuration;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * 网络连接监听线程
 */
public class MasterConnectionListener extends Thread {

    /**
     * 默认的监听端口号的重试次数
     */
    public static final int DEFAULT_RETRIES = 3;

    /**
     * 网络连接监听服务器
     */
    private ServerSocket serverSocket;
    /**
     * 当前系统是否正在运行中
     */
    private volatile boolean running = true;
    /**
     * 当前已经尝试重试监听端口号的次数
     */
    private int retries = 0;

    /**
     * 线程的运行逻辑
     */
    @Override
    public void run() {
        // 只要系统还在运行，而且监听端口号的重试次数小于默认重试次数
        while (running && retries <= DEFAULT_RETRIES) {
            try {
                // 获取master节点内部网络通信的端口号
                int port = getMasterNetworkPort();
                InetSocketAddress endpoint = new InetSocketAddress(port);
                // 基于ServerSocket监听master节点内部网络通信的端口号
                this.serverSocket = new ServerSocket();
                // 端口复用，对于TIME_WAIT状态的连接所占用的端口，允许系统也作为bind()操作的候选端口
                this.serverSocket.setReuseAddress(true);
                this.serverSocket.bind(endpoint);

                // 跟发起连接请求的master建立网络连接
                while (running) {
                    // id比自己大的master节点发送网络连接请求过来
                    // 在这里会成功建立网路连接
                    Socket socket = this.serverSocket.accept();
                    socket.setTcpNoDelay(true); // 网络通信不允许延迟
                    socket.setSoTimeout(0); // 读取数据时的超时时间为0，没有超时，阻塞读取
                    // 为建立好的网络连接，启动IO线程
                    startIOThreads(socket);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 为建立好的网络连接，启动IO线程
     *
     * @param socket
     */
    private void startIOThreads(Socket socket) {
        new MasterNetworkWriteThread(socket).start();
        new MasterNetworkReadThread(socket).start();
    }

    /**
     * 获取master节点之间进行网络通信的端口号
     *
     * @return
     */
    private int getMasterNetworkPort() {
        Configuration configuration = Configuration.getInstance();

        Integer myNodeId = configuration.getNodeId();

        // 下面的地址是：1:127.0.0.1:2156:2356:2556,2:127.0.0.1:2157:2357:2557,3:127.0.0.1:2158:2358:2558
        String masterNodeServers = configuration.getMasterNodeServers();
        String[] masterNodeServersSplited = masterNodeServers.split(",");

        for (String masterNodeServer : masterNodeServersSplited) {
            // 切分后的地址是：1:127.0.0.1:2156:2356:2556
            String[] masterNodeServerSplited = masterNodeServer.split(":");
            Integer nodeId = Integer.valueOf(masterNodeServerSplited[0]);

            if (myNodeId.equals(nodeId)) {
                // 最终返回的是2156这种master内部通信端口号
                return Integer.valueOf(masterNodeServerSplited[2]);
            }
        }

        return 0;
    }

    /**
     * 关闭当前线程
     */
    public void shutdown() {
        this.running = false;
    }

}
